import { BaseType, Selection } from 'd3-selection';
import L, { LatLng } from 'leaflet';
import { FeatureJson, FeatureOption, FeatureType } from '../types';
import { getAnotation } from '../util/util';

abstract class Feature {
    /**
     * 要素ID
     */
    id: string;
    /**
     * 单个要素需要的属性
     */
    featureOption: FeatureOption;
    /**
     * 要素类型：point | line | polygon
     */
    type: FeatureType;

    /**
     * 要素的地理坐标，之所以这么包装，是为了兼容点、线、面
     */
    latLngs: LatLng[][];

    /**
     * 属性信息
     */
    properties?: any;
    /**
     * 单个要素组：该组包含高亮，描边，顶层要素
     */
    // @ts-ignore
    group: Selection<SVGGElement | BaseType, Feature, BaseType, string>;

    private clickTime: number = 0;
    private downPosition: { x: number; y: number } = { x: 0, y: 0 }; // mousedown 的位置

    private eventTimer: NodeJS.Timeout | undefined = undefined; // 单击事件的处理方法

    latestUseTime: number;

    /**
     * 构造函数
     * @param core 核心存储对象
     * @param latLngs 地理坐标
     * @param featureType 要素类型
     * @param properties 要素属性
     */
    constructor(
        id: string,
        featureOption: FeatureOption,
        latLngs: LatLng[][],
        featureType: FeatureType,
        properties?: any
    ) {
        this.id = id;
        this.featureOption = featureOption;
        this.type = featureType;
        this.properties = properties;
        this.latLngs = this.generateLatLng(latLngs);
        this.latestUseTime = Date.now();
    }

    generateLatLng(latLngs: LatLng[][]) {
        return latLngs.map((arr) => {
            return arr
                .filter((latLng) => latLng && latLng.lat && latLng.lng)
                .map((latLng) => L.latLng(latLng.lat, latLng.lng));
        });
    }

    /**
     * 向要素组中添加要素
     * @param groupSelection 当前要素的组选择器
     * @param shape 当前要素对象
     */
    abstract add(groupSelection: Selection<SVGGElement | BaseType, Feature, BaseType, string>): Feature;

    /**
     * 单个要素重绘，重新计算地理位置,地图缩放/撤销
     */
    abstract redraw(): Feature;

    addEventListener() {
        this.group
            .on('mousedown', this.mousedown.bind(this))
            .on('mousemove', this.mousemove.bind(this))
            .on('mouseup', this.mouseup.bind(this))
            .on('mouseout', this.mouseout.bind(this))
            .on('click', this.click.bind(this))
            .on('dblclick', this.dblclick.bind(this))
            .on('contextmenu', this.contextmenu.bind(this));
    }

    contextmenu(e: MouseEvent, feature: Feature) {
        // 屏蔽浏览器右键菜单
        e.preventDefault();
        e.stopPropagation();
        this.featureOption.eventBus.emit('contextmenu', feature.instance(), e);
    }
    mousedown(e: MouseEvent, feature: Feature) {
        e.stopPropagation();

        if (this.featureOption.mode.getType() !== 'draw' && e.button !== 2) {
            this.downPosition = { x: e.offsetX, y: e.offsetY };
        }

        const { featureMousedown } = feature.featureOption;

        featureMousedown(e, feature);
    }

    mousemove(e: MouseEvent, feature: Feature) {
        this.execMouseEvent(e, feature);

        e.stopPropagation();
    }

    mouseup(e: MouseEvent, feature: Feature) {
        // 标绘模式下，不往外抛点击事件
        // if (this.featureOption.mode.getType() !== 'draw' && e.button !== 2) {
        //     // 判断是否在同一个位置松开鼠标
        //     if (this.downPosition.x === e.offsetX && this.downPosition.y === e.offsetY) {
        //         const shicha = new Date().getTime() - this.clickTime;
        //         if (shicha > 500) {
        //             // 单击
        //             this.eventTimer = setTimeout(() => {
        //                 const event = this.getEventInfo(e);
        //                 event.type = 'click';
        //                 this.featureOption.eventBus.emit(event.type, feature.instance(), event);
        //             }, 250);
        //         } else {
        //             //双击
        //             if (this.eventTimer) {
        //                 clearTimeout(this.eventTimer);
        //             }

        //             const event = this.getEventInfo(e);
        //             event.type = 'dblclick';
        //             this.featureOption.eventBus.emit(event.type, feature.instance(), event);
        //         }
        //         this.clickTime = new Date().getTime();
        //     }
        // }
        // 不需要阻止冒泡,graphic graphic需要响应事件addhsitory
        this.execMouseEvent(e, feature);
    }

    mouseout(e: MouseEvent, feature: Feature) {
        this.execMouseEvent(e, feature);
    }

    dblclick(e: MouseEvent, feature: Feature) {
        e.stopPropagation();
        this.featureOption.eventBus.emit(e.type, feature.instance(), e);
    }
    click(e: MouseEvent, feature: Feature) {
        e.stopPropagation();
        this.featureOption.eventBus.emit(e.type, feature.instance(), e);
        this.execMouseEvent(e, feature);
    }

    execMouseEvent(e: any, feature: Feature) {
        const modeType = this.featureOption.mode.getType();
        const type: string = e.type;
        const mouseMethod = 'feature' + type.charAt(0).toUpperCase() + type.slice(1);

        if ((this.featureOption as any).hasOwnProperty(mouseMethod)) {
            // TODO
            // console.log(feature.type + ':' + mouseMethod);

            // layer层有事件监听的，执行相关事件
            if (modeType === 'draw' || modeType === 'drag') {
                (this.featureOption as any)[mouseMethod](e, feature);
            }
        }
    }

    /**
     * 拖动时改变要素经纬度
     * @param latlng
     * @param indexs 要素中的第几个经纬度需要被替换
     * @param sequenceNum 拖动的点，在二维数组中的层级顺序
     */
    abstract changeLatlng(latLng: LatLng, indexs: number[], sequenceNum: number): void;

    abstract remove(): void;

    //生成对外展示的要素实例
    abstract instance(): any;

    /**
     *改变要素id
     * @param id 新的id
     */
    changeId(id: string): Feature {
        this.featureOption.changeFeatureId(this.id, id);
        return this;
    }

    getBounds() {
        const latLngs: LatLng[] = this.latLngs.flat();
        return L.latLngBounds(latLngs);
    }
    setProperties(prop: any) {
        this.featureOption.addHistory(
            {
                annotation: getAnotation('add base ', this),
                features: [this],
            },
            true
        );
        this.properties = prop;

        this.featureOption.addHistory({
            annotation: getAnotation('set properties ', this),
            features: [this],
        });
    }

    /**
     * 将当前要素转成json对象
     */
    json(): FeatureJson {
        const latLngs = this.type === 'polygon' ? this.latLngs : this.latLngs[0];
        return {
            id: this.id,
            latLngs,
            type: this.type,
            properties: this.properties,
        };
    }

    asGeoJSON() {}

    /**
     * 判断经纬度集合是否为空
     * @returns
     */
    isEmpty() {
        return this.latLngs.flat().length === 0;
    }

    /**
     * click 事件和dblclick是从mouseUp事件中产生的，e.type是只读属性，所以需要单独提取属性组成新的事件对象
     * @param e 要素点击事件的参数
     * @returns
     */
    getEventInfo(e: MouseEvent) {
        const {
            clientX,
            clientY,
            movementX,
            movementY,
            offsetX,
            offsetY,
            pageX,
            pageY,
            screenX,
            screenY,
            x,
            y,
            relatedTarget,
            currentTarget,
            target,
        } = e;

        return {
            type: '',
            clientX,
            clientY,
            movementX,
            movementY,
            offsetX,
            offsetY,
            pageX,
            pageY,
            screenX,
            screenY,
            x,
            y,
            relatedTarget,
            currentTarget,
            target,
            stopPropagation: e.stopPropagation.bind(e),
            stopImmediatePropagation: e.stopImmediatePropagation.bind(e),
            preventDefault: e.preventDefault.bind(e),
        };
    }

    /**
     * 获取实例类型
     * @returns 'cross', 'vertex', 'point', 'line', 'polygon'
     */
    getInstanceType() {
        let type;
        const featureClassName = this.group.attr('class');
        // 优先判断cross，因为包含cross的 肯定包含vertex
        const types = ['cross', 'vertex', 'point', 'line', 'polygon'];
        for (const item of types) {
            if (featureClassName.includes(item)) {
                type = item;
                break;
            }
        }
        return type;
    }

    /**
     * 对当前要素的属性信息进行深拷贝
     * @returns
     */
    clone() {
        return JSON.parse(JSON.stringify(this.json()));
    }
}

export default Feature;
